﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Dokan;
using RemoteCode.Attributes;

namespace MirrorDrive.Operations.Cache
{
    public class CacheOperations : DokanOperations
    {
        private DokanOperations _prevLayer;
        private CacheSystem _cache;

        public CacheOperations(DokanOperations prevLayer)
        {
            _prevLayer = prevLayer;
            _cache = new CacheSystem();
        }

       
        public int CreateFile(string filename, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileMode mode, System.IO.FileOptions options, DokanFileInfo info)
        {
            ICacheEntry entry = _cache.Find(filename);
            CreateFileReturn cfr = new CreateFileReturn();
            cfr.Access = access;
            cfr.Mode = mode;
            cfr.Access = access;
            cfr.Options = options;

            if (entry != null && entry.CreateFile.Count > 0)
            {
               // Logger.Info(new StackTrace().GetFrame(0).GetMethod().Name + " cache used: " + filename);

                info.IsDirectory = entry.GetType() == typeof(DirectoryEntry);

                lock (entry.CreateFile)
                {
                    foreach (CreateFileReturn c in entry.CreateFile)
                        if (cfr.Compare(c))
                        {
                            return c.ReturnValue;
                        }
                }
            } 

            int ret = _prevLayer.CreateFile(filename, access, share, mode, options, info);

            if (!info.IsDirectory && ret != 0)
            {
                entry = _cache.CacheFile(filename, true);

                cfr.ReturnValue = ret;

                lock (entry.CreateFile)
                {
                    entry.CreateFile.Add(cfr);
                }
            }
            else if (info.IsDirectory)
            {
                entry = _cache.CacheDirectory(filename, true);

                cfr.ReturnValue = ret;

                lock (entry.CreateFile)
                {
                    entry.CreateFile.Add(cfr);
                }
            }

            return ret;
        }

       
        public int OpenDirectory(string filename, DokanFileInfo info)
        {
            DirectoryEntry entry = _cache.CacheDirectory(filename, true);

            if (!entry.OpenDirectory.IsSet)
                entry.OpenDirectory.ReturnValue = _prevLayer.OpenDirectory(filename, info);

          //  Logger.Info(new StackTrace().GetFrame(0).GetMethod().Name + " cache used: " + filename);
            return entry.OpenDirectory.ReturnValue;
        }

       
        public int CreateDirectory(string filename, DokanFileInfo info)
        {
            DirectoryEntry entry = _cache.CacheDirectory(filename.Substring(0, filename.LastIndexOf("\\") + 1), false);

            if (entry != null)
            {
                entry.ResetCreateFile();
                entry.Parent.ResetFindFiles();
            }

            return _prevLayer.CreateDirectory(filename, info);
        }

       
        public int Cleanup(string filename, DokanFileInfo info)
        {
            return _prevLayer.Cleanup(filename, info);
        }

       
        public int CloseFile(string filename, DokanFileInfo info)
        {
            return _prevLayer.CloseFile(filename, info);           
        }

       
        public int ReadFile(string filename, ref byte[] buffer, ref uint readBytes, long offset, DokanFileInfo info)
        {
            return _prevLayer.ReadFile(filename, ref buffer, ref readBytes, offset, info);              
        }

       
        public int WriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info)
        {
            return _prevLayer.WriteFile(filename, buffer, ref writtenBytes, offset, info);             
        }

       
        public int FlushFileBuffers(string filename, DokanFileInfo info)
        {
            return _prevLayer.FlushFileBuffers(filename, info);            
        }

       
        public int GetFileInformation(string filename, ref FileInformation fileinfo, DokanFileInfo info)
        {
            ICacheEntry entry = null;

            if (info.IsDirectory)
                entry = _cache.CacheDirectory(filename, true);
            else 
                entry = _cache.CacheFile(filename, true);

            if (entry.GetFileInformation.IsSet)
            {
               // Logger.Info(new StackTrace().GetFrame(0).GetMethod().Name + " cache used: " + filename);

                fileinfo.Attributes = entry.GetFileInformationValue.Attributes;
                fileinfo.CreationTime = entry.GetFileInformationValue.CreationTime;
                fileinfo.FileName = entry.GetFileInformationValue.FileName;
                fileinfo.LastAccessTime = entry.GetFileInformationValue.LastAccessTime;
                fileinfo.LastWriteTime = entry.GetFileInformationValue.LastWriteTime;
                fileinfo.Length = entry.GetFileInformationValue.Length;

                return entry.GetFileInformation.ReturnValue;
            }

           entry.GetFileInformation.ReturnValue = _prevLayer.GetFileInformation(filename, ref fileinfo, info);
           entry.GetFileInformationValue = fileinfo;
           return entry.GetFileInformation.ReturnValue;
        }

       
        public int FindFiles(string filename, ref System.Collections.ArrayList files, DokanFileInfo info)
        {
            DirectoryEntry entry = _cache.CacheDirectory(filename, true);

            if (entry.FindFiles.IsSet)
            {
               // Logger.Info(new StackTrace().GetFrame(0).GetMethod().Name + " cache used: " + filename);

                for (int i = 0; i < entry.FindFilesValue.Count; i++)
                {
                    files.Add(entry.FindFilesValue[i]);
                }

                return entry.FindFiles.ReturnValue;
            }

            entry.FindFiles.ReturnValue = _prevLayer.FindFiles(filename, ref files, info);
            entry.FindFilesValue = files;
            return entry.FindFiles.ReturnValue;
        }

       
        public int SetFileAttributes(string filename, System.IO.FileAttributes attr, DokanFileInfo info)
        {
            ICacheEntry entry = _cache.Find(filename);

            if (entry != null)
                entry.ResetGetFileInformation();

            return _prevLayer.SetFileAttributes(filename, attr, info);              
        }

       
        public int SetFileTime(string filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info)
        {
            ICacheEntry entry = _cache.Find(filename);

            if (entry != null)
                entry.ResetGetFileInformation();

            return _prevLayer.SetFileTime(filename, ctime, atime, mtime, info);              
        }

       
        public int DeleteFile(string filename, DokanFileInfo info)
        {
            FileEntry entry = _cache.CacheFile(filename, false);

            if (entry != null)
                entry.Parent.ResetFindFiles();

            return _prevLayer.DeleteFile(filename, info); 
        }

       
        public int DeleteDirectory(string filename, DokanFileInfo info)
        {
            DirectoryEntry entry = _cache.CacheDirectory(filename, false);

            if (entry != null)
                entry.Parent.ResetFindFiles();

            return _prevLayer.DeleteDirectory(filename, info);             
        }

       
        public int MoveFile(string filename, string newname, bool replace, DokanFileInfo info)
        {
            FileEntry entry = _cache.CacheFile(filename, false);

            if (entry != null)
            {
                entry.ResetAll();
                entry.Parent.ResetFindFiles();
            }

            entry = _cache.CacheFile(newname, false);

            if (entry != null)
                entry.Parent.ResetFindFiles();

            return _prevLayer.MoveFile(filename, newname, replace, info);  
        }

       
        public int SetEndOfFile(string filename, long length, DokanFileInfo info)
        {
            FileEntry entry = _cache.CacheFile(filename, false);

            if (entry != null)
                entry.ResetGetFileInformation();

            return _prevLayer.SetEndOfFile(filename, length, info);              
        }

       
        public int SetAllocationSize(string filename, long length, DokanFileInfo info)
        {
            FileEntry entry = _cache.CacheFile(filename, false);

            if (entry != null)
                entry.ResetGetFileInformation();

            return _prevLayer.SetEndOfFile(filename, length, info); 
        }

       
        public int LockFile(string filename, long offset, long length, DokanFileInfo info)
        {
            FileEntry entry = _cache.CacheFile(filename, false);

            if (entry != null)
                entry.ResetCreateFile();

            return _prevLayer.LockFile(filename, offset, length, info);             
        }

       
        public int UnlockFile(string filename, long offset, long length, DokanFileInfo info)
        {
            FileEntry entry = _cache.CacheFile(filename, false);

            if (entry != null)
                entry.ResetCreateFile();

            return _prevLayer.UnlockFile(filename, offset, length, info);                  
        }

       
        public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes, ref ulong totalFreeBytes, DokanFileInfo info)
        {
            return _prevLayer.GetDiskFreeSpace(ref freeBytesAvailable, ref totalBytes, ref totalFreeBytes, info);    
        }

       
        public int Unmount(DokanFileInfo info)
        {
            _cache.Clear();
            return _prevLayer.Unmount(info);
        }
    }
}
